#include "TableFile.h"
#include <ext/stdio_sync_filebuf.h>

using namespace std;

TableFile::TableFile(bool trunc) {
	string path = this->getDefaultTableFile();
	this->init(path, trunc);
}

TableFile::TableFile(string path, bool trunc) {
	this->init(path, trunc);
}

void TableFile::init(string path, bool trunc) {
	if (trunc) {
		// Abre el archivo en modo lectura - escritura binario.
		this->file.open(path.c_str(), ios::in | ios::out | ios::binary | ios::trunc);
	} else {
		this->file.open(path.c_str(), ios::in | ios::out | ios::binary);
	}

	// Determina si tuvo éxito la apertura del archivo.
	if (!this->file.is_open()) {
		// Limpia los flags de control de estado del archivo.
		this->file.clear();

		// Crea el archivo.
		this->file.open(path.c_str(), ios::out | ios::binary);
		this->file.close();

		if (trunc) {
			this->file.open(path.c_str(), ios::in | ios::out | ios::binary | ios::trunc);
		} else {
			// Reabre el archivo para lectura - escritura binario.
			this->file.open(path.c_str(), ios::in | ios::out | ios::binary);
		}

		// Verifica que haya podido crear el archivo.
		if (!this->file.is_open()) {
		  // Arroja una excepcion.
		  throw string("El archivo no pudo ser abierto");
		}
	}

	typedef basic_filebuf<char, char_traits<char> > filebuf_t;
	filebuf_t* bbuf = dynamic_cast<filebuf_t*>(this->file.rdbuf());
	if (bbuf != NULL) {
		// This subclass is only there for accessing the FILE*.
		struct my_filebuf : public basic_filebuf<char, char_traits<char> > {
			int fd() { return this->_M_file.fd(); }
		};

		this->fd = static_cast<my_filebuf*>(bbuf)->fd();
	}

	this->fl.l_type = F_WRLCK;
	this->fl.l_whence = SEEK_SET;
	this->fl.l_start = 0;
	this->fl.l_len = 0;
	this->fl.l_pid = getpid();

	this->path = path;
}

TableFile::~TableFile() {
	// Cierra el archivo.
	this->file.close();
}

string TableFile::getDefaultTableFile() {
	mkdir(ROOTDIRECTORY, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);

    return DEFAULTTABLEFILEPATH;
}

TableRecord TableFile::getNextRecord() {
	TableRecord record;

	// Verifica que el archivo esté abierto.
	if (this->file.is_open()) {
		// Lee del archivo un registro.
		this->file.read(reinterpret_cast<char*>(&record), sizeof(TableRecord));

		// Chequea si se ha producido un error.
		if (this->file.fail()) {
			// Arroja una excepción ante la imposibilidad de leer un reg.
			throw string("No se pudo leer correctamente el registro");
		}

		return record;
	} else {
		// Arroja una excepción porque el archivo no está abierto.
		throw string("El archivo no está abierto");
	}
}

void TableFile::addRecord(TableRecord record) {
	// Verifica que el archivo esté abierto.
	if (this->file.is_open()) {
		// Escribe el registro en el archivo.
		this->file.write(reinterpret_cast<char*>(&record), sizeof(TableRecord));

		// Chequea si se ha producido un error.
		if (this->file.fail()) {
		  // Arroja una excepción ante la imposibilidad de escribir el reg.
		  throw string("No se pudo escribir correctamente el registro");
		}
	}
	else {
		// Arroja una excepción porque el archivo no está abierto.
		throw string("El archivo no está abierto");
	}
}

bool TableFile::isEOF() {
	// Para comprobar el fin lee un char del buffer, sin retirarlo y lo
	// compara con el fin de archivo.
	int peeked = this->file.peek();
	bool isEOF = (peeked == char_traits<char>::eof());

	if (isEOF) {
		// Si llegó al fin del archivo limpia los flags.
		this->file.clear();
	}

	return isEOF;
}

long int TableFile::currentPosition() {
	long int pos = 0;

	// Verifica que el archivo este abierto.
	if (this->file.is_open()) {
		// Calcula el número de registro segun la posicion del byte actual.
		pos = this->file.tellg() / sizeof(TableRecord);
	} else {
		// Arroja una excepción porque el archivo no esta abierto.
		throw string("El archivo no está abierto");
	}

	return pos;
}

void TableFile::goTo(long int position) {
	// Verifica que el archivo esté abierto.
	if (this->file.is_open()) {
		// Mueve la posición actual según sea el tamano del registro.
		this->file.seekg(position * sizeof(TableRecord), ios_base::beg);

		// Chequea si se ha producido un error.
		if (this->file.fail()) {
		  // Arroja una excepción ante la imposibilidad de leer un reg.
		  throw string("No se pudo posicionar correctamente el registro");
		}
	} else {
		// Arroja una excepción porque el archivo no está abierto.
		throw string("El archivo no está abierto");
	}
}

void TableFile::goToEnd() {
	// Verifica que el archivo este abierto.
	if (this->file.is_open()) {
		// Mueve la posición actual según sea el tamano del registro.
		this->file.seekg(0 * sizeof(TableRecord), ios_base::end);

		// Chequea si se ha producido un error.
		if (this->file.fail()) {
		  // Arroja una excepción ante la imposibilidad de leer un reg.
		  throw string("No se pudo posicionar correctamente el registro");
		}
	} else {
		// Arroja una excepción porque el archivo no está abierto.
		throw string("El archivo no está abierto");
	}
}

int TableFile::getWriteLock () {
	this->fl.l_type = F_WRLCK;
	int result = fcntl(this->fd, F_SETLKW, &(this->fl));
	return result;
}

int TableFile::getReadLock () {
	this->fl.l_type = F_RDLCK;
	int result = fcntl(this->fd, F_SETLKW, &(this->fl));
	return result;
}

int TableFile::unlock () {
	this->fl.l_type = F_UNLCK;
	int result = fcntl(this->fd, F_SETLK, &(this->fl));
	return result;
}
